home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / Palettes / CustomMenuPalette / CustomMenu / CustomMenu.subproj / DraggableMenu.m < prev    next >
Text File  |  1995-09-17  |  10KB  |  363 lines

  1. // Written by Gideon King
  2.  
  3. // Caveats and options for improvement
  4. // - If a target or action changes due to a program update, the affected menu items won't work
  5. // - Could be extended to allow user re-ordering of menus - you'd need to save the whole menu structure though,
  6. //   and watch out for program updates.
  7. // - You could ask the users to name their custom menus when you create new ones.
  8.  
  9. //
  10. // Source file      : DraggableMenu.m
  11. // Created by       : gideon@berd
  12. // Created on       : Fri Sep 8 16:18:54 NZST 1995
  13. // RCS File         : $Source: /Ramoth/Black.Albatross/CVS/CustomMenu/CustomMenu.subproj/DraggableMenu.m,v $
  14. // Last modified    : $Date: 1995/09/10 22:38:07 $
  15. // Last modified by : $Author: gideon $
  16. // Current Revision : $Revision: 1.1.1.1 $
  17. //
  18.  
  19. static const char RCSId[] = "$Id: DraggableMenu.m,v 1.1.1.1 1995/09/10 22:38:07 gideon Exp $";
  20.  
  21.  
  22. #import "DraggableMenu.h"
  23. #import "CustomMenu.h"
  24.  
  25. // My private pasteboard for menu cells
  26. const char *MHMenuCellPboardType = "MenuHack Menu Cell version 1.0";
  27.  
  28. // This has to be defined here so we don't have an instance variable, and therefore can't poseAs menu
  29. BOOL __saveAndRestoreFlag__ = YES;
  30. BOOL __haveUnarchivedMenu__ = NO;
  31.  
  32. @implementation DraggableMenu
  33.  
  34. - (const char *)version
  35. {
  36.     return RCSId;
  37. }
  38.  
  39. - setSaveAndRestore:(BOOL)yesNo
  40. {
  41.     __saveAndRestoreFlag__ = yesNo;
  42.     return self;
  43. }
  44.  
  45. - (BOOL)getSaveAndRestore
  46. {
  47.     return __saveAndRestoreFlag__;
  48. }
  49.  
  50. - awakeFromNib
  51. {
  52.     char fileName[MAXPATHLEN+1];
  53.     NXStream *stream;
  54.     
  55.     if (__saveAndRestoreFlag__ && !__haveUnarchivedMenu__)
  56.     {
  57.         __haveUnarchivedMenu__ = YES;
  58.         // Load the custom menus from file
  59.         sprintf(fileName, "%s/.NeXT/%s.customMenus", NXHomeDirectory(), [NXApp appName]);    
  60.  
  61.         stream = NXMapFile(fileName, NX_READONLY);
  62.         
  63.         if (stream)
  64.         {
  65.             NX_DURING
  66.                 NXTypedStream *ts = NXOpenTypedStream(stream, NX_READONLY);
  67.                 if (ts)
  68.                 {
  69.                     id mainItemList = [[NXApp mainMenu] itemList];
  70.                     int i, count;
  71.                     NXReadType(ts, "i", &count);
  72.                     for (i = 0; i < count; i++)
  73.                     {
  74.                         id newItem = NXReadObject(ts);
  75.                         [mainItemList insertRowAt:0];
  76.                         [mainItemList putCell:newItem at:0 :0];
  77.                     }
  78.                     [[NXApp mainMenu] setItemList:mainItemList];
  79.                     [[NXApp mainMenu] display];
  80.                     NXCloseTypedStream(ts);
  81.                 }
  82.                 else
  83.                 {
  84.                     fprintf(stderr, "Error opening %s for reading.\n", fileName);
  85.                 }
  86.                 NXCloseMemory(stream, NX_FREEBUFFER);            
  87.             NX_HANDLER
  88.                 fprintf(stderr, "Error reading file %s.\n", fileName);
  89.             NX_ENDHANDLER
  90.         }
  91.     }
  92.  
  93.     return self;
  94. }
  95.  
  96. // This is a nasty hack - because we are using poseAs to take over from Menu, we can't add any new methods
  97. // that you can connect to in IB, so I've taken over the performClose mathod. Oh well, I guess the whole thing
  98. // is a nasty hack anyway, so who cares???
  99. - performClose:sender
  100. {
  101.     char fileName[MAXPATHLEN+1];
  102.     NXTypedStream *ts;
  103.  
  104.     if (strcmp([[sender selectedCell] title], "Quit") == 0)
  105.     {
  106.         if (__saveAndRestoreFlag__)
  107.         {
  108.             // Write the custom menus to file
  109.             sprintf(fileName, "%s/.NeXT/%s.customMenus", NXHomeDirectory(), [NXApp appName]);
  110.         
  111.             ts = NXOpenTypedStreamForFile(fileName, NX_WRITEONLY);
  112.             if (ts)
  113.             {
  114.                 NX_DURING
  115.                     int i, count = 0;
  116.                     id mainItemList = [[NXApp mainMenu] itemList];
  117.                     
  118.                     // First go through and find out how many menu cells we have to archive    
  119.                     for (i = 0; i < [mainItemList cellCount]; i++)
  120.                     {
  121.                         id thisCell = [mainItemList cellAt:i :0];
  122.                         // We have given the menus we have added a tag of 42131 to identify them
  123.                         if(strstr([thisCell title], "Custom Menu") && ([thisCell tag] == 42131))
  124.                         {
  125.                             count++;
  126.                         }
  127.                     }
  128.                     
  129.                     NXWriteType(ts, "i", &count);
  130.                     
  131.                     // Now archive the cells in reverse to make them easier to unarchive
  132.                     for (i = count-1; i >= 0; i--)
  133.                     {
  134.                         NXWriteRootObject(ts, [mainItemList cellAt:i :0]);
  135.                     }
  136.                     NXCloseTypedStream(ts);
  137.                 NX_HANDLER
  138.                     fprintf(stderr, "Error writing file %s.\n", fileName);
  139.                 NX_ENDHANDLER
  140.             }
  141.             else
  142.             {
  143.                 fprintf(stderr, "Error opening %s for writing.\n", fileName);
  144.             }
  145.         }
  146.         
  147.         return [NXApp terminate:sender];
  148.     }
  149.     
  150.     return [super performClose:sender];
  151. }
  152.  
  153. // Pick up an Alternate-drag of a menu item, and start a dragging session
  154. - mouseDown:(NXEvent *)theEvent
  155. {        
  156.     // Only do the dragging if an Alternate key is down
  157.     if (theEvent->flags & NX_ALTERNATEMASK)
  158.     {
  159.         NXImage *theImage;
  160.         id theCell = [matrix selectedCell];
  161.         int theRow = [matrix selectedRow];
  162.         NXBitmapImageRep *newRep = nil;
  163.         
  164.            NXPoint zero = {0.0, 0.0};
  165.         NXPoint offset;
  166.         id     thePboard;
  167.         NXRect myRect;
  168.         NXRect matrixFrame;
  169.  
  170.         NXStream *stream;
  171.         NXTypedStream *ts;
  172.         
  173.         static NXCursor *cursor = nil;
  174.         NXPoint spot;
  175.         
  176.         // Work out the size and position of the menu cell.
  177.         [matrix getCellFrame:&myRect at:theRow :0];
  178.  
  179.         theImage = [[NXImage alloc] initSize:&(myRect.size)];
  180.         
  181.         // Draw the button into the imagerep
  182.         [matrix lockFocus];
  183.         newRep = [[NXBitmapImageRep alloc] initData:NULL fromRect:&myRect];
  184.         [matrix unlockFocus];
  185.         
  186.         [matrix getFrame:&matrixFrame];
  187.  
  188.         [theImage useRepresentation:newRep];
  189.         offset.x = 0;
  190.         offset.y = theEvent->location.y + 
  191.             ((NX_HEIGHT(&matrixFrame) - NX_Y(&myRect)) - theEvent->location.y)
  192.             - NX_HEIGHT(&myRect);
  193.  
  194.         // get the Pboard and put the menu cell on to it.
  195.         thePboard = [Pasteboard newName:NXDragPboard];
  196.         [thePboard declareTypes:&MHMenuCellPboardType num:1 owner:self];
  197.         
  198.         // Write the cell to the pasteboard
  199.         if (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) 
  200.         {
  201.             if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) 
  202.             {
  203.                 NXWriteRootObject(ts, theCell);
  204.                 NXCloseTypedStream(ts);
  205.             }
  206.                [thePboard writeType:MHMenuCellPboardType fromStream:stream];
  207.             NXCloseMemory(stream, NX_FREEBUFFER);
  208.         }
  209.    
  210.            if (!cursor)
  211.         {
  212.             NXImage *cursorImage = [NXImage findImageNamed:"lightningCursor"];
  213.             if (cursorImage)
  214.             {
  215.                 cursor = [[NXCursor alloc] initFromImage:cursorImage];
  216.                 spot.x = 8.0; spot.y = 8.0;
  217.                 [cursor setHotSpot:&spot];
  218.             }
  219.         }
  220.         if (cursor)
  221.             [cursor push];
  222.             
  223.         // Start the drag
  224.         [self dragImage:theImage    // visible on screen during drag
  225.             at:&offset                 // offset of the mouse point for the drag
  226.             offset:&zero            // offset of the inital mouse pt
  227.             event:theEvent            // theEvent structure
  228.             pasteboard:thePboard    // a Pasteboard with data on it
  229.             source:self                // source object
  230.             slideBack:NO];            // if no destination don't animate back to source - make a new menu
  231.         
  232.         if (cursor)
  233.             [cursor pop];
  234.  
  235.         [theImage free];
  236.     }
  237.     else
  238.     {
  239.         [super mouseDown:theEvent];
  240.     }
  241.     return self;
  242. }
  243.  
  244. // Methods to be the source of a dragging operation
  245. - draggedImage:(NXImage *)image beganAt:(NXPoint *)screenPoint
  246. {
  247.     return self;
  248. }
  249.  
  250. - (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
  251. {
  252.     return (NX_DragOperationCopy | NX_DragOperationGeneric);
  253. }
  254.  
  255. // Make a new menu if the user dropped the menu item anywhere not on a CustomMenu
  256. - draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint deposited:(BOOL)flag
  257. {
  258.     if (!flag)    // They just dragged it out into thin air
  259.     {
  260.         int retval;
  261.         retval = NXRunAlertPanel("Custom Menu", "Would you like to create a new custom menu?", "Yes", "No", NULL);
  262.         
  263.         if (retval == NX_ALERTDEFAULT)    // Yes make a new one
  264.         {
  265.             // Check whether there are any other custom menus already defined
  266.             
  267.             // This will have to be changed if you are asking the users for a menu name
  268.             int i, count = 1;
  269.             id draggedCell = [matrix selectedCell];
  270.             char titleString[128];
  271.             id newMenu;
  272.             id newItem;
  273.             id mainItemList = [[NXApp mainMenu] itemList];
  274.             id itemList;
  275.             List *menuAndItemList = [[List alloc] init];
  276.             
  277.             for (i = 0; i < [mainItemList cellCount]; i++)
  278.             {
  279.                 id thisCell = [mainItemList cellAt:i :0];
  280.                 // We have given the menus we have added a tag of 42131 to identify them
  281.                 if(strstr([thisCell title], "Custom Menu") && ([thisCell tag] == 42131))
  282.                 {
  283.                     count++;
  284.                 }
  285.             }
  286.             
  287.             sprintf(titleString, "Custom Menu %d", count);
  288.             
  289.             // Create a new menu in the main menu
  290.             newItem = [[MenuCell alloc] initTextCell:titleString];
  291.             [newItem setTag:42131];
  292.             [mainItemList insertRowAt:count-1];
  293.             [mainItemList putCell:newItem at:count-1 :0];
  294.             [menuAndItemList addObject:mainItemList];
  295.             [menuAndItemList addObject:newItem];
  296.             
  297.             // Create the new submenu
  298.             newMenu = [[CustomMenu alloc] initTitle:titleString];
  299.             
  300.             // Add the dragged menu item to the new menu
  301.             itemList = [newMenu itemList];
  302.             [itemList insertRowAt:0];
  303.             [itemList putCell:draggedCell at:0 :0];
  304.             [newMenu setItemList:itemList];
  305.             
  306.             // Attach the new submenu to the main menu
  307.             [menuAndItemList addObject:newMenu];
  308.             
  309.             // This is done in this way to avoid removing the view from the view hierarchy while lockfocused.
  310.             [self perform:@selector(attachSubmenuUsing:) with:menuAndItemList afterDelay:0 cancelPrevious:YES];
  311.         }
  312.     }
  313.     return self;
  314. }
  315.  
  316. - (BOOL)ignoreModifierKeysWhileDragging
  317. {
  318.     return YES;
  319. }
  320.  
  321. - (BOOL)isDraggingSourceLocal
  322. {
  323.     return YES;
  324. }
  325.  
  326. // Method to avoid getting DPS errors - see draggedImage:endedAt:deposited:
  327. - attachSubmenuUsing:aList
  328. {
  329.     id itemList = [aList objectAt:0];
  330.     id newItem = [aList objectAt:1];
  331.     id newMenu = [aList objectAt:2];
  332.     
  333.     [[NXApp mainMenu] setItemList:itemList];
  334.     [[NXApp mainMenu] display];
  335.     
  336.     // Attach the new submenu to the main menu
  337.     [[NXApp mainMenu] setSubmenu:newMenu forItem:newItem];
  338.     
  339.     [aList free];
  340.     
  341.     return self;
  342. }
  343.  
  344. - write:(NXTypedStream *)stream
  345. {
  346.     [super write:stream];
  347.     NXWriteType(stream, "c", &__saveAndRestoreFlag__);
  348.     return self;
  349. }
  350.  
  351. - read:(NXTypedStream *)stream
  352. {
  353.     [super read:stream];
  354.     NXReadType(stream, "c", &__saveAndRestoreFlag__);
  355.  
  356.     // The poseas has to happen at this point so the read and write methods work, with the right super-class
  357.     if (![NXApp respondsTo:@selector(isTestingInterface)])    // We are not in IB
  358.             [[DraggableMenu class] poseAs:[Menu class]];
  359.     return self;
  360. }
  361.  
  362. @end
  363.